package com.lyx.question.service.impl;

import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.lyx.common.constant.ESConstant;
import com.lyx.common.constant.QuestionConstant;
import com.lyx.common.constant.RedisContant;
import com.lyx.common.exception.BusinessException;
import com.lyx.common.exception.ExceptionEnum;
import com.lyx.common.utils.PageUtils;
import com.lyx.question.dto.ESQuestionDto;
import com.lyx.question.dto.SearchResult;
import com.lyx.question.entity.ESQuestion;
import com.lyx.question.entity.Question;
import com.lyx.question.entity.Subject;
import com.lyx.question.mapper.QuestionMapper;
import com.lyx.question.mapper.SubjectMapper;
import com.lyx.question.service.QuestionService;
import com.lyx.question.vo.QuestionVo;
import com.lyx.question.vo.SearchParam;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author 14667
 * @date 2022/7/20 15:32
 */
@Component
public class QuestionServiceImpl extends ServiceImpl<QuestionMapper, Question> implements QuestionService {
    @Autowired
    SubjectMapper subjectMapper;

    @Autowired
    RestHighLevelClient restHighLevelClient;

    @Autowired
    StringRedisTemplate redisTemplate;
    @Override
    public boolean addQuestion(QuestionVo questionVo) {
        Question byId = baseMapper.selectById(questionVo.getId());
        if (Objects.isNull(byId)){
            Subject subject = subjectMapper.selectById(questionVo.getChapterId());
            if (Objects.isNull(subject)){
                throw new BusinessException(ExceptionEnum.NOT_EXIST.getCode(),String.format(ExceptionEnum.NOT_EXIST.getMsg(),"章节"));
            }
            Question question = new Question();
            BeanUtils.copyProperties(questionVo,question);
            if(questionVo.getQuestionType()==0){
                question.setQuestionScore(QuestionConstant.SINGLE_SELECTION_SCORE);
            }else{
                question.setQuestionScore(QuestionConstant.MULTIPLE_SELECTION_SCORE);
            }
            int i = baseMapper.insert(question);
            if (i<=0){
                return false;
            }
            //将题目上传到es里面
            ESQuestion esQuestion = new ESQuestion();
            BeanUtils.copyProperties(question, esQuestion);
            IndexRequest indexRequest = new IndexRequest(ESConstant.ES_INDEX);
            indexRequest.id(esQuestion.getId().toString());
            String jsonStr = JSONUtil.toJsonStr(esQuestion);
            indexRequest.source(jsonStr, XContentType.JSON);
            try {
                IndexResponse index = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
                System.out.println(index.status());
            } catch (IOException e) {
                e.printStackTrace();
                throw new BusinessException(ExceptionEnum.ES_SAVE_ERROR);
            }
            return true;
        }else{
            throw new BusinessException(ExceptionEnum.QUESTION_EXIST);
        }
    }
    @Override
    public boolean deleteQuestion(Integer id) {
        if(Objects.isNull(id)){
            throw new BusinessException(ExceptionEnum.IS_NOT_NULL.getCode(),String.format(ExceptionEnum.IS_NOT_NULL.getMsg(),"id"));
        }
        Question byId = baseMapper.selectById(id);
        if(Objects.isNull(byId)){
            throw new BusinessException(ExceptionEnum.NOT_EXIST.getCode(),String.format(ExceptionEnum.NOT_EXIST.getMsg(),"题目"));
        }
        int i = baseMapper.deleteById(id);
        if(i<=0){
            return false;
        }
        //es进行删除
        DeleteRequest deleteRequest = new DeleteRequest(ESConstant.ES_INDEX,id.toString());
        try {
            DeleteResponse response = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
            System.out.println(response.status());
        } catch (IOException e) {
            throw new BusinessException(ExceptionEnum.ES_DELETE_ERROR);
        }

        return true;
    }

    @Override
    public PageUtils selectQuestionByIdAndChapterId(Integer id, Integer chapterId, Integer currentPage, Integer size) {
        QueryWrapper<Question> wrapper = new QueryWrapper<>();
        if(Objects.nonNull(id)){
            wrapper.eq("id",id);
        }
        if(Objects.nonNull(chapterId)){
            wrapper.eq("chapter_id",chapterId);
        }

        Page<Question> page = new Page<>(currentPage, size);
        baseMapper.selectPage(page, wrapper);
//        page.getRecords().stream().map(item->{
//            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(item.getCreateTime())
//        })
        PageUtils pageUtils = new PageUtils(page);
        return pageUtils;
    }

    @Override
    public PageUtils listWithPage(Integer currentPage, Integer size) {
        Page<Question> questionPage = new Page<>(currentPage,size);
        baseMapper.selectPage(questionPage,null);
        PageUtils pageUtils = new PageUtils(questionPage);
        return pageUtils;
    }

    @Override
    public boolean updateQuestion(QuestionVo questionVo) {
        Question byId = baseMapper.selectById(questionVo.getId());
        if(Objects.isNull(byId)){
            throw new BusinessException(ExceptionEnum.QUESTION_NOT_EXIST);
        }
        Subject subject = subjectMapper.selectById(questionVo.getChapterId());
        if (Objects.isNull(subject)){
            throw new BusinessException(ExceptionEnum.NOT_EXIST.getCode(),String.format(ExceptionEnum.NOT_EXIST.getMsg(),"章节"));
        }
        Question question = new Question();
        BeanUtils.copyProperties(questionVo,question);
        if(questionVo.getQuestionType()==0){
            question.setQuestionScore(QuestionConstant.SINGLE_SELECTION_SCORE);
        }else{
            question.setQuestionScore(QuestionConstant.MULTIPLE_SELECTION_SCORE);
        }

        int i = baseMapper.updateById(question);
        if(i<=0){
            return false;
        }
        //将题目上传到es里面
        ESQuestion esQuestion = new ESQuestion();
        BeanUtils.copyProperties(question, esQuestion);
        UpdateRequest updateRequest = new UpdateRequest();
        updateRequest.index(ESConstant.ES_INDEX);
        updateRequest.id(esQuestion.getId().toString());
        String jsonStr = JSON.toJSONString(esQuestion);
        updateRequest.doc(jsonStr,XContentType.JSON);

        try {
            restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
            throw new BusinessException(ExceptionEnum.ES_SAVE_ERROR);
        }
        return true;
    }

    @Override
    public void saveAll() {
        List<Question> questionList = baseMapper.selectList(null);
        BulkRequest bulkRequest = new BulkRequest();
        for(Question question: questionList){
            ESQuestion esQuestion = new ESQuestion();
            BeanUtils.copyProperties(question, esQuestion);
            IndexRequest indexRequest = new IndexRequest(ESConstant.ES_INDEX);
            indexRequest.id(esQuestion.getId().toString());
            String s1 = JSON.toJSONString(esQuestion);
            indexRequest.source(s1,XContentType.JSON);
            bulkRequest.add(indexRequest);
        }
        try {
            BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
            if (bulk.hasFailures()){
                throw new BusinessException(ExceptionEnum.ES_SAVE_ERROR);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public SearchResult indexSearch(String text,long currentPage,long size) {
        SearchRequest searchRequest = new SearchRequest(ESConstant.ES_INDEX);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        if(NumberUtils.isDigits(text)){
            boolQueryBuilder
                    .should(QueryBuilders.termQuery("id", text));
        }
        if(!StringUtils.isEmpty(text)){
            boolQueryBuilder.should(QueryBuilders.matchQuery("questionDescription", text));
        }
        searchSourceBuilder.query(boolQueryBuilder);
        //排序
        searchSourceBuilder.sort("id", SortOrder.ASC);
        //分页
        long from = (currentPage-1)*size;
        searchSourceBuilder.from((int)(from));
        searchSourceBuilder.size((int)(size));
        searchRequest.source(searchSourceBuilder);
        SearchResponse response = null;
        try {
            response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new BusinessException(ExceptionEnum.ES_SEARCH_ERROR);
        }
        long value = response.getHits().getTotalHits().value;
        long totalPage = value%size==0?value/size:(value/size+1);
        SearchResult searchResult = new SearchResult();
        searchResult.setPageSize(size);
        searchResult.setPageNum(currentPage);
        searchResult.setTotalCount(value);
        searchResult.setTotalPage(totalPage);
        SearchHit[] hits = response.getHits().getHits();
        ArrayList<ESQuestionDto> result = new ArrayList<>();
        if(hits==null || hits.length==0){
            return searchResult;
        }
        for(SearchHit hit : hits){
            String sourceAsString = hit.getSourceAsString();
            ESQuestionDto esQuestion = JSON.parseObject(sourceAsString, ESQuestionDto.class);
            result.add(esQuestion);
        }
        searchResult.setResult(result);
        return searchResult;
    }



    @Override
    public void updateRank(){
        LocalDate now = java.time.LocalDate.now();
        LocalDate last = now.minusDays(1);
        LocalDate last2 = now.minusDays(2);
        LocalDate last7 = now.minusDays(7);
        LocalDate last8 = now.minusDays(8);
        Set<ZSetOperations.TypedTuple<String>> todayTotal = new HashSet<>();
        Set<ZSetOperations.TypedTuple<String>> lastTotal = new HashSet<>();
        //如果前2天的总排名为空，说明之前就没数据，只需要统计后面的
        if(redisTemplate.opsForZSet().size(RedisContant.QUESTION_NUM_WEEK_RANK + "_" +Objects.toString(last2))==0){
            //如果前一天不为空，将前一天的总的设为当天的，否则前一天就不放数据
            if(redisTemplate.opsForZSet().size(Objects.toString(last))>0){
                redisTemplate.opsForZSet().add(RedisContant.QUESTION_NUM_WEEK_RANK +"_" +Objects.toString(last),redisTemplate.opsForZSet().rangeWithScores(Objects.toString(last),0,-1));
                redisTemplate.expire(RedisContant.QUESTION_NUM_WEEK_RANK + "_"+Objects.toString(last),7, TimeUnit.DAYS);
            }
        }else{
            //先把前一天的总排名清空，准备重新计算
            getNewTotal(last, last2, last8, lastTotal);
        }
        //进行到这一步  ，前一天的总的一定就是正确的了，当然前一天可能也没有数据
        getNewTotal(now,last,last7,todayTotal);


    }

    private void getNewTotal(LocalDate last, LocalDate last2, LocalDate last8,  Set<ZSetOperations.TypedTuple<String>> lastTotal) {
        redisTemplate.opsForZSet().removeRange(RedisContant.QUESTION_NUM_WEEK_RANK+"_"+Objects.toString(last),0,-1);
        Set<ZSetOperations.TypedTuple<String>> day8Set = redisTemplate.opsForZSet().rangeWithScores(Objects.toString(last8), 0, -1);
        Set<ZSetOperations.TypedTuple<String>> lastDayTotalSet = redisTemplate.opsForZSet().rangeWithScores(RedisContant.QUESTION_NUM_WEEK_RANK +"_"+ Objects.toString(last2), 0, -1);
        if(!CollectionUtils.isEmpty(lastDayTotalSet)){
            if(!io.jsonwebtoken.lang.Collections.isEmpty(day8Set)){
                ImmutableMap<String, ZSetOperations.TypedTuple<String>> day8Map = Maps.uniqueIndex(day8Set, ZSetOperations.TypedTuple::getValue);
                for (ZSetOperations.TypedTuple<String> stringTypedTuple : lastDayTotalSet) {
                    String value = stringTypedTuple.getValue();
                    Double score = stringTypedTuple.getScore();
                    Double score1 = day8Map.getOrDefault(value, new DefaultTypedTuple<String>(value, 0d)).getScore();
                    DefaultTypedTuple<String> objectDefaultTypedTuple = new DefaultTypedTuple<>(value, score - score1);
                    lastTotal.add(objectDefaultTypedTuple);
                }
                redisTemplate.opsForZSet().add(RedisContant.QUESTION_NUM_WEEK_RANK +"_" +Objects.toString(last), lastTotal);
                redisTemplate.expire(RedisContant.QUESTION_NUM_WEEK_RANK +"_"+ Objects.toString(last),7, TimeUnit.DAYS);

            }else{
                redisTemplate.opsForZSet().add(RedisContant.QUESTION_NUM_WEEK_RANK +"_" +Objects.toString(last),lastDayTotalSet);
                redisTemplate.expire(RedisContant.QUESTION_NUM_WEEK_RANK +"_"+ Objects.toString(last),7, TimeUnit.DAYS);
            }
        }
        //加上当天的 如果有数据的话
        if(redisTemplate.opsForZSet().size(Objects.toString(last))>0){
            Set<ZSetOperations.TypedTuple<String>> lastSet = redisTemplate.opsForZSet().rangeWithScores(Objects.toString(last), 0, -1);
            for (ZSetOperations.TypedTuple<String> stringTypedTuple : lastSet){
                String value = stringTypedTuple.getValue();
                Double score = stringTypedTuple.getScore();
                redisTemplate.opsForZSet().incrementScore(RedisContant.QUESTION_NUM_WEEK_RANK +"_"+ Objects.toString(last),value,score);
            }
        }
    }
}
